<?php

declare(strict_types=1);

namespace app\service\user;

use app\model\User;
use app\model\UserAuth;
use app\model\SysMenu;
use app\service\dto\ResultInterface;
use app\service\dto\LoginSuccessResult;
use app\service\dto\PartialAuthResult;
use app\service\dto\ErrorResult;
use app\service\dto\TokenPair;
use app\service\user\login\LoginFactory;
use app\util\RouteUtil;
use think\Collection;
use think\Request;

class AuthService
{
    public function __construct(
        private readonly VerifyService $verifyService,
        private readonly TokenService $tokenService,
        private readonly PartialTokenService $partialTokenService,
        private readonly Request $request
    ) {}

    public function findUserById(int $userId): ?User
    {
        return User::with([
            'profile',
            'identities.org',
            'identities.region',
            'company',
            'roles',
        ])->find($userId);
    }

    public function validatePartialToken(string $token): array
    {
        return $this->partialTokenService->validatePartialToken($token);
    }

    /**
     * 使用工厂模式登录并根据校验结果生成对应的返回结构
     */
    public function login(string $type, array $credentials): ResultInterface
    {
        try {
            $loginResult = LoginFactory::create($type)->login($credentials);
        } catch (\Throwable $e) {
            return new ErrorResult($e->getMessage(), 400);
        }

        if (!isset($loginResult['status'])) {
            return new ErrorResult('登录返回结果格式不正确', 500);
        }

        $verify = $this->verifyService->verifyUser($loginResult);
        $user = $loginResult['user'] ?? null;
        $code = (int) ($verify['code'] ?? 401);
        $message = (string) ($verify['msg'] ?? '');

        return match ($code) {
            200 => $user instanceof User
                ? $this->buildLoginSuccess($user)
                : new ErrorResult('用户信息缺失，无法完成登录', 500),
            4001 => $this->buildNeedBindPhoneResult($user, $message),
            4002 => $this->buildNeedVerifyIdentityResult($user, $message),
            default => new ErrorResult(
                $message !== '' ? $message : '登录失败',
                $code > 0 ? $code : 401
            ),
        };
    }

    public function getName(User $user): string
    {
        $identity = strtolower((string) ($this->primaryIdentity($user) ?? ''));

        return match (true) {
            $identity === 'admin' => $this->formatAdminName($user),
            $this->isOperatorIdentity($identity) => $this->formatOperatorName($user),
            $this->isBuyerIdentity($identity) => $this->formatBuyerName($user),
            $this->isSellerIdentity($identity) => $this->formatOrgUserName($user),
            default => $this->formatDefaultName($user),
        };
    }

    /**
     * 获取用户组标识（用于 Gateway 分组）
     */
    public function getUserGroup(User $user): string
    {
        $groups = $this->getGatewayGroups($user);
        return $groups[0] ?? 'seller';
    }

    public function getGatewayGroups(User $user): array
    {
        $client = $this->resolveClientForUser($user);
        $groups = ['user_' . (int) $user->id];

        switch ($client) {
            case 'buyer':
                $groups[] = 'buyer';
                break;
            case 'seller':
                $groups = array_merge($groups, $this->resolveSellerGroups($user));
                break;
            default:
                $groups[] = $client;
                break;
        }

        return array_values(array_unique(array_filter($groups, static fn($value) => is_string($value) && $value !== '')));
    }

    /**
     * 获取用户角色列表
     *
     * @return array<int, string>
     */
    public function getUserRoles(User $user): array
    {
        $roles = $user->roles()->column('slug');
        return is_array($roles) ? array_values($roles) : [];
    }

    /**
     * 获取用户基本资料
     *
     * @return array<string, mixed>
     */
    public function getUserProfileData(User $user): array
    {
        $profile = $user->profile;

        return [
            'id' => (int) $user->id,
            'phone' => (string) $user->phone,
            'status' => (int) $user->status,
            'nickname' => $profile->nickname ?? '',
            'real_name' => $profile->real_name ?? '',
            'avatar' => $profile->avatar ?? '',
        ];
    }

    /**
     * 获取用户身份详情
     *
     * @return array<int, array<string, mixed>>
     */
    public function getUserIdentitiesData(User $user): array
    {
        return $user->identities()
            ->with(['org', 'region'])
            ->select()
            ->map(function ($identity) {
                $org = $identity->org;
                $region = $identity->region;

                return [
                    'id' => (int) $identity->id,
                    'type' => (string) $identity->identity_type,
                    'status' => (int) $identity->status,
                    'org_id' => $identity->org_id,
                    'org_name' => $org?->name,
                    'region_id' => $identity->region_id,
                    'region_name' => $region?->name,
                    'verified_at' => $identity->verified_at,
                ];
            })
            ->toArray();
    }

    public function getUserCompanyData(?User $user): ?array
    {
        if (!$user) {
            return null;
        }

        $company = $user->company;
        if (!$company) {
            return null;
        }

        return [
            'company_name' => $company->company_name,
            'license_code' => $company->license_code,
            'license_photo' => $company->license_photo,
            'legal_rep' => $company->legal_rep,
            'address' => $company->address,
            'scope' => $company->scope,
            'status' => (int) $company->status,
            'verified_time' => $company->verified_at,
        ];
    }

    public function buildLoginSuccess(User $user): LoginSuccessResult
    {
        $fullUser = $this->findUserById((int) $user->id);
        if ($fullUser instanceof User) {
            $user = $fullUser;
        }

        $gatewayGroups = $this->getGatewayGroups($user);
        $primaryGroup = $gatewayGroups[0] ?? null;
        $client = $this->resolveClientForUser($user);

        $payload = [
            'user_id' => (int) $user->id,
            'groups' => $gatewayGroups,
        ];
        $tokens = $this->tokenService->generateTokens($payload);

        return new LoginSuccessResult(
            $tokens->getAccessToken(),
            $tokens->getRefreshToken(),
            $tokens->getExpires(),
            (string) $user->phone,
            $this->getName($user),
            $this->getUserRoles($user),
            $this->getUserPermissionsCode($user),
            $this->getUserMenus($user),
            $this->getUserProfileData($user),
            $this->getUserIdentitiesData($user),
            $gatewayGroups,
            $client
        );
    }

    public function buildPartialAuthResult(array $payload, string $action, string $message, array $context = []): PartialAuthResult
    {
        $payloadWithAction = array_merge($payload, ['partial_action' => $action]);
        $tokens = $this->partialTokenService->generatePartialTokenData($payloadWithAction);
        /** @var PartialTokenData $tokens */
        return new PartialAuthResult(
            $tokens->getAccessToken(),
            $tokens->getExpires(),
            $action,
            $message,
            array_merge($context, ['partial_action' => $action])
        );
    }

    /**
     * 获取用户有权限访问的按钮标识
     *
     * @return array<int, string>
     */
    public function getUserPermissionsCode(User $user): array
    {
        $permissionCodes = $this->collectPermissionCodes($user);

        return SysMenu::where('status', 1)
            ->where('type', 'button')
            ->where(function ($query) use ($permissionCodes) {
                $query->whereNull('permission_code');
                if (!empty($permissionCodes)) {
                    $query->whereOr('permission_code', 'in', $permissionCodes);
                }
            })
            ->column('identifier') ?: [];
    }

    /**
     * 获取用户有权限访问的菜单路由
     *
     * @return array<int, array<string, mixed>>
     */
    public function getUserMenus(User $user): array
    {
        $permissionCodes = $this->collectPermissionCodes($user);
        $client = $this->resolveClientForUser($user);

        $menus = SysMenu::where('status', 1)
            ->where('type', 'menu')
            ->where('client_type', $client)
            ->where(function ($query) use ($permissionCodes) {
                $query->whereNull('permission_code');
                if (!empty($permissionCodes)) {
                    $query->whereOr('permission_code', 'in', $permissionCodes);
                }
            })
            ->order('sort')
            ->select()
            ->toArray();

        return RouteUtil::format($menus);
    }

    public function refreshToken(string $token): TokenPair
    {
        return $this->tokenService->validateRefreshToken($token);
    }

    public function logout(int $userId, bool $isAll = false): bool
    {
        return $this->tokenService->invalidateUserTokens($userId, $isAll);
    }

    public function websocketLogin(string $token): array
    {
        return $this->tokenService->validateAccessToken($token);
    }

    private function buildNeedBindPhoneResult(mixed $user, string $message): ResultInterface
    {
        $identifier = '';
        $identityType = '';
        $unionId = null;
        $metadata = null;

        if ($user instanceof UserAuth) {
            $identifier = (string) $user->identifier;
            $identityType = (string) $user->identity_type;
            $unionId = $user->unionid;
            $metadata = $user->metadata;
        } elseif ($user instanceof User) {
            $identifier = (string) $user->phone;
            $identityType = UserAuth::TYPE_SMS;
        } elseif (is_array($user)) {
            $identifier = (string) ($user['identifier'] ?? '');
            $identityType = (string) ($user['identity_type'] ?? '');
            $unionId = $user['unionid'] ?? null;
            $metadata = $user['metadata'] ?? null;
        }

        if ($identifier === '' || $identityType === '') {
            return new ErrorResult('缺少绑定手机号所需的凭证信息', 4001);
        }

        $payload = array_filter([
            'identifier' => $identifier,
            'identity_type' => $identityType,
            'unionid' => $unionId,
            'metadata' => $metadata,
        ], static fn($value) => $value !== null && $value !== '');

        $context = array_filter([
            'identifier' => $identifier,
            'identity_type' => $identityType,
            'unionid' => $unionId,
        ], static fn($value) => $value !== null && $value !== '');

        return $this->buildPartialAuthResult(
            $payload,
            'need_bind_phone',
            $this->resolvePartialMessage($message, 'need_bind_phone'),
            $context
        );
    }

    private function buildNeedVerifyIdentityResult(mixed $user, string $message): ResultInterface
    {
        if (!$user instanceof User) {
            return new ErrorResult('缺少用户身份信息，无法完成审核指引', 4002);
        }

        return $this->buildPartialAuthResult(
            [
                'user_id' => (int) $user->id,
                'group' => 'need_verify',
            ],
            'need_verify_identity',
            $this->resolvePartialMessage($message, 'need_verify_identity'),
            [
                'user_id' => (int) $user->id,
                'identities' => $this->getUserIdentitiesData($user),
                'company' => $this->getUserCompanyData($user),
            ]
        );
    }

    /**
     * 组装权限代码列表，避免重复查询
     *
     * @return array<int, string>
     */
    private function collectPermissionCodes(User $user): array
    {
        $roles = $user->roles()->with('permissions')->select();
        $permissionCodes = [];

        foreach ($roles as $role) {
            foreach ($role->permissions as $permission) {
                if ($permission->code) {
                    $permissionCodes[] = $permission->code;
                }
            }
        }

        return array_values(array_unique($permissionCodes));
    }

    private function resolvePartialMessage(string $message, string $action): string
    {
        if (trim($message) !== '') {
            return $message;
        }

        return $action === 'need_bind_phone' ? '请先绑定手机号' : '请完善认证资料';
    }

    private function formatOperatorName(User $user): string
    {
        $regionUser = $user->primaryRegion();
        if ($regionUser && $regionUser->region) {
            return sprintf('%s操作员', $regionUser->region->name);
        }

        return '操作员';
    }

    private function formatOrgUserName(User $user): string
    {
        $orgMember = $user->orgMembers()->with('org')->find();
        if ($orgMember && $orgMember->org) {
            $position = $orgMember->position ?: '成员';
            return $orgMember->org->name . $position;
        }

        return '企业用户';
    }

    private function formatBuyerName(User $user): string
    {
        $profile = $user->profile;
        if ($profile && $profile->real_name) {
            return (string) $profile->real_name;
        }
        if ($profile && $profile->nickname) {
            return (string) $profile->nickname;
        }
        return '买家用户';
    }

    private function formatAdminName(User $user): string
    {
        $profile = $user->profile;
        if ($profile && $profile->real_name) {
            return (string) $profile->real_name;
        }
        $roles = $this->getUserRoles($user);
        if (!empty($roles)) {
            return '系统用户';
        }
        return '管理员';
    }

    private function formatDefaultName(User $user): string
    {
        $profile = $user->profile;
        return $profile && $profile->nickname ? (string) $profile->nickname : '用户';
    }

    private function resolveClient(): string
    {
        $mark = $this->request->client_mark ?? null;
        if (is_string($mark) && $mark !== '') {
            return strtolower($mark);
        }

        $header = (string) $this->request->header('X-Client', '');
        if ($header !== '') {
            return strtolower($header);
        }

        return 'seller';
    }

    private function resolveClientForUser(User $user): string
    {
        $client = $this->resolveClient();

        if ($this->hasAdminPrivileges($user)) {
            return 'admin';
        }

        if ($client === 'buyer') {
            return 'buyer';
        }

        return 'seller';
    }

    private function primaryIdentity(User $user): ?string
    {
        $identities = null;

        if (method_exists($user, 'getRelation')) {
            try {
                $identities = $user->getRelation('identities');
            } catch (\Throwable $e) {
                $identities = null;
            }
        }

        if (!($identities instanceof Collection) || $identities->isEmpty()) {
            $identities = $user->identities()
                ->order('status', 'desc')
                ->order('id', 'asc')
                ->select();
            if ($identities instanceof Collection) {
                $user->setRelation('identities', $identities);
            }
        }

        if (!($identities instanceof Collection) || $identities->isEmpty()) {
            return null;
        }

        foreach ($identities as $identity) {
            if ((int) $identity->status === 1) {
                return strtolower((string) $identity->identity_type);
            }
        }

        $first = $identities->first();
        return $first ? strtolower((string) $first->identity_type) : null;
    }

    private function isSellerIdentity(?string $identity): bool
    {
        if ($identity === null) {
            return false;
        }

        return in_array($identity, ['seller', 'both', 'merchant'], true) || str_contains($identity, 'seller');
    }

    private function isBuyerIdentity(?string $identity): bool
    {
        if ($identity === null) {
            return false;
        }

        return in_array($identity, ['buyer', 'individual'], true) || str_contains($identity, 'buyer');
    }

    private function isOperatorIdentity(?string $identity): bool
    {
        if ($identity === null) {
            return false;
        }

        return in_array($identity, ['support', 'operator'], true)
            || str_contains($identity, 'support')
            || str_contains($identity, 'operator');
    }

    private function resolveSellerGroups(User $user): array
    {
        $groups = [];

        if ($this->hasAdminPrivileges($user)) {
            $groups[] = 'admin';
            $primaryRegion = $user->primaryRegion();
            if ($primaryRegion && $primaryRegion->region) {
                $groups[] = 'admin_' . (int) $primaryRegion->region->id;
            }
            return $groups;
        }

        $orgId = $this->resolveSellerOrgId($user);
        if ($orgId !== null) {
            $groups[] = 'seller_' . $orgId;
        } else {
            $groups[] = 'seller_user_' . (int) $user->id;
        }

        return $groups;
    }

    private function resolveSellerOrgId(User $user): ?int
    {
        $orgMember = $user->orgMembers()
            ->with('org')
            ->where('status', 1)
            ->find();

        if ($orgMember && $orgMember->org) {
            return (int) $orgMember->org->id;
        }

        return null;
    }

    private function hasAdminPrivileges(User $user): bool
    {
        $adminIdentities = ['admin', 'support'];
        $identity = $user->identities()
            ->whereIn('identity_type', $adminIdentities)
            ->where('status', 1)
            ->find();

        if ($identity) {
            return true;
        }

        $roles = array_map('strtolower', $this->getUserRoles($user));
        $adminRoles = ['admin', 'support', 'system_admin'];

        foreach ($roles as $role) {
            if (in_array($role, $adminRoles, true)) {
                return true;
            }
        }

        return false;
    }
}
